﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#include "GLMesh.h"
#include "GLObjectManager.h"

namespace UX_STL
{
namespace GLShell
{
//-------------------------- GLBuffer -----------------------------//
/*
=================================================
	конструктор
=================================================
*/
	GLBuffer::GLBuffer():
		_uSize(0), _uID(0), _eTarget(gl_buffer::_UNKNOWN)
	{
	}
		
/*
=================================================
	деструктор
=================================================
*/
	GLBuffer::~GLBuffer()
	{
		if ( _uID != 0 )
		{
			GLOM()->DeleteBuffers( 1, &_uID );
			_uID = 0;
		}
	}
	
/*
=================================================
	создает буфер
=================================================
*/
	bool GLBuffer::Create(gl_buffer::type eTarget)
	{
		Destroy();
		_eTarget = eTarget;
		CHECK_ERR( GLOM()->GenBuffers( 1, &_uID ) );
		return true;
	}
	
/*
=================================================
	удаляет буфер
=================================================
*/
	void GLBuffer::Destroy()
	{
		if ( _uID != 0 )
		{
			GLOM()->DeleteBuffers( 1, &_uID );
			_uID = 0;
		}

		_uSize	 = 0;
		_eTarget = gl_buffer::_UNKNOWN;
	}

/*
=================================================
	проверяет установлен ли буфер
=================================================
*/
	bool GLBuffer::IsBinded() const
	{
		GLint	i_buffer_binding = 0;
		GLenum	e_target		 = 0;

		if ( _eTarget == gl_buffer::ARRAY )				e_target = GL_ARRAY_BUFFER_BINDING;					else
		if ( _eTarget == gl_buffer::ELEMENT_ARRAY )		e_target = GL_ELEMENT_ARRAY_BUFFER_BINDING;			else
		//if ( _eTarget == gl_buffer::TEXTURE )			e_target = GL_TEXTURE_BUFFER_BINDING;				else
		if ( _eTarget == gl_buffer::UNIFORM )			e_target = GL_UNIFORM_BUFFER_BINDING;				else
		if ( _eTarget == gl_buffer::COPY_READ )			e_target = GL_COPY_READ_BUFFER_BINDING;				else
		if ( _eTarget == gl_buffer::COPY_WRITE )		e_target = GL_COPY_WRITE_BUFFER_BINDING;			else
		if ( _eTarget == gl_buffer::TRANSFORM_FEEDBACK )e_target = GL_TRANSFORM_FEEDBACK_BUFFER_BINDING;	else
		if ( _eTarget == gl_buffer::PIXEL_PACK )		e_target = GL_PIXEL_PACK_BUFFER_BINDING;			else
		if ( _eTarget == gl_buffer::PIXEL_UNPACK )		e_target = GL_PIXEL_UNPACK_BUFFER_BINDING;			else
			return false;

		GL_CALL( glGetIntegerv( e_target, &i_buffer_binding ) );
		return ( _uID == i_buffer_binding );
	}

/*
=================================================
	копировать данные из буфера
=================================================
*/
	bool GLBuffer::Copy(const GLBuffer *pBuffer, usize uReadOffset, usize uWriteOffset, usize uSize)
	{
		if ( uSize == 0 )
			uSize = Min( pBuffer->Size() - uReadOffset, Size() - uWriteOffset );
		else
		{
			if ( uSize + uReadOffset > pBuffer->Size() )
				uSize = pBuffer->Size() - uReadOffset;
			
			if ( uSize + uWriteOffset > Size() )
				uSize = Size() - uWriteOffset;
		}

		GL_CALL_R( glNamedCopyBufferSubData( pBuffer->Id(), _uID, uReadOffset, uWriteOffset, uSize ) );
		return true;
	}

/*
=================================================
	передает данные в буфер
=================================================
*/
	bool GLBuffer::SetData(const void *pData, usize uSize, gl_usage::type eUsage)
	{
		CHECK_ERR( pData != null and uSize > 0 );

		GL_CALL_R( glNamedBufferData( _uID, uSize, pData, eUsage ) );
		_uSize = uSize;
		return true;
	}

/*
=================================================
	записывает данные в буфер
=================================================
*/
	bool GLBuffer::SubData(const void *pData, usize uSize, usize uOffset)
	{
		CHECK_ERR( uSize + uOffset <= _uSize );
		GL_CALL_R( glNamedBufferSubData( _uID, uOffset, uSize, pData ) );
		return true;
	}

/*
=================================================
	изменяет размер буфера не сохраняя данные
=================================================
*/
	bool GLBuffer::Allocate(usize uNewSize, gl_usage::type eUsage)
	{
		CHECK_ERR( uNewSize > 0 );

		GL_CALL_R( glNamedBufferData( _uID, uNewSize, null, eUsage ) );
		_uSize = uNewSize;
		return true;
	}
	
/*
=================================================
	возвращает часть буфера
=================================================
*/
	bool GLBuffer::GetSubData(void *pData, usize uOffset, uint uSize) const
	{
		ASSERT( uSize + uOffset <= _uSize );
		GL_CALL_R( glGetNamedBufferSubData( _uID, uOffset, uSize, pData ) );
		return true;
	}



//-------------------------- GLSubMesh ----------------------------//
#ifdef UX_GL_USE_VAO
/*
=================================================
	конструктор
=================================================
*/
	GLSubMesh::GLSubMesh():
		_uVAO(0), _uOffset(0), _uCount(0), _uIBO(0),
		_ePrimitive(gl_primitive::TRIANGLE), _eIndex(gl_index::UINT_32)
	{
	}

	GLSubMesh::GLSubMesh(GLSubMesh &cSM):
		_uVAO(cSM._uVAO), _uOffset(cSM._uOffset), _uCount(cSM._uCount), _uIBO(cSM._uIBO),
		_ePrimitive(cSM._ePrimitive), _eIndex(cSM._eIndex), _aVAInfo(cSM._aVAInfo)
	{
		cSM._uVAO = 0;
		cSM.Destroy();
	}

/*
=================================================
	деструктор
=================================================
*/
	GLSubMesh::~GLSubMesh()
	{
		if ( _uVAO != 0 )
		{
			GLOM()->DeleteVertexArrays( 1, &_uVAO );
			_uVAO = 0;
		}
	}

/*
=================================================
	создать VAO
=================================================
*/
	bool GLSubMesh::Create(const uni_array<const GLVertexAttribExt> &aAttribs, GLuint uIBO, usize uCount,
							usize uOffset, gl_primitive::type eMode, gl_index::type eType)
	{
		CHECK_ERR( !aAttribs.Empty() );

		Destroy();
		CHECK_ERR( GLOM()->GenVertexArrays( 1, &_uVAO ) );
		CHECK_ERR( GLOM()->SetAttribs( _uVAO, aAttribs ) );

		_uIBO		= uIBO;
		_uOffset	= uOffset;
		_uCount		= uCount;
		_ePrimitive	= eMode;
		_eIndex		= eType;

		FOR( i, _aVAInfo ) {
			_aVAInfo[i] = i < aAttribs.Count() ? GLVertexAttribInfo( aAttribs[i] ) : GLVertexAttribInfo();
		}

		return true;
	}
	
/*
=================================================
	создать VAO
=================================================
*/
	bool GLSubMesh::Create(const uni_array<const GLVertexAttrib> &aAttribs, GLuint uVBO, GLuint uIBO, usize uCount,
							usize uOffset, gl_primitive::type eMode, gl_index::type eType)
	{
		CHECK_ERR( !aAttribs.Empty() );
		
		Destroy();

		va_array_t	a_attribs;

		FOR( i, a_attribs ) {
			if ( i >= aAttribs.Count() ) break;
			a_attribs[i] = GLVertexAttribExt( uVBO, aAttribs[i] );
			_aVAInfo[i]  = i < aAttribs.Count() ? GLVertexAttribInfo( aAttribs[i] ) : GLVertexAttribInfo();
		}

		CHECK_ERR( GLOM()->GenVertexArrays( 1, &_uVAO ) );
		CHECK_ERR( GLOM()->SetAttribs( _uVAO, a_attribs ) );
		
		_uIBO		= uIBO;
		_uOffset	= uOffset;
		_uCount		= uCount;
		_ePrimitive	= eMode;
		_eIndex		= eType;

		return true;
	}
	
/*
=================================================

=================================================
*/
	bool GLSubMesh::GetParams(GLVerticesAttribsExt_t &aAttribs, GLuint &uIBO, usize &uCount,
								usize &uOffset, gl_primitive::type &eMode, gl_index::type &eType) const
	{
		CHECK_ERR( GLOM()->s_GetAttribs( _uVAO, aAttribs ) );
		uIBO	= _uIBO;
		uCount	= _uCount;
		uOffset	= _uOffset;
		eMode	= _ePrimitive;
		eType	= _eIndex;
		return true;
	}
	
/*
=================================================
	удалить VAO
=================================================
*/
	void GLSubMesh::Destroy()
	{
		if ( _uVAO != 0 )
		{
			GLOM()->DeleteVertexArrays( 1, &_uVAO );
			_uVAO = 0;
		}
		
		_uIBO		= 0;
		_uOffset	= 0;
		_uCount		= 0;
		_ePrimitive	= gl_primitive::TRIANGLE;
		_eIndex		= gl_index::UINT_32;
		
		FOR( i, _aVAInfo ) {
			_aVAInfo[i] = GLVertexAttribInfo();
		}
	}

#else

/*
=================================================
	конструктор
=================================================
*/
	GLSubMesh::GLSubMesh():
		_aVAO(), _uIBO(0), _uOffset(0), _uCount(0),
		_ePrimitive(gl_primitive::TRIANGLE), _eIndex(gl_index::UINT_32)
	{
	}

	GLSubMesh::GLSubMesh(const GLSubMesh &cSM):
		_aVAO(cSM._aVAO), _uIBO(cSM._uIBO), _uOffset(cSM._uOffset), _uCount(cSM._uCount),
		_ePrimitive(cSM._ePrimitive), _eIndex(cSM._eIndex), _aVAInfo(cSM._aVAInfo)
	{
		cSM.Destroy();
	}

/*
=================================================
	деструктор
=================================================
*/
	GLSubMesh::~GLSubMesh()
	{
	}

/*
=================================================
	создать VAO
=================================================
*/
	bool GLSubMesh::Create(const uni_array<const GLVertexAttribExt> &aAttribs, GLuint uIBO, usize uCount,
							usize uOffset, gl_primitive::type eMode, gl_index::type eType)
	{
		STATIC_ASSERT( va_info_array_t::COUNT == va_array_t::COUNT, "arrays must be same size!" );

		CHECK_ERR( !aAttribs.Empty() );
		
		FOR( i, _aVAO ) {
			_aVAO[i]	= i < aAttribs.Count() ? aAttribs[i] : GLVertexAttribExt();
			_aVAInfo[i]	= i < aAttribs.Count() ? GLVertexAttribInfo( aAttribs[i] ) : GLVertexAttribInfo();
		}

		_uIBO		= uIBO;
		_uOffset	= uOffset;
		_uCount		= uCount;
		_ePrimitive	= eMode;
		_eIndex		= eType;

		return true;
	}
	
/*
=================================================
	создать VAO
=================================================
*/
	bool GLSubMesh::Create(const uni_array<const GLVertexAttrib> &aAttribs, GLuint uVBO, GLuint uIBO, usize uCount,
							usize uOffset, gl_primitive::type eMode, gl_index::type eType)
	{
		CHECK_ERR( !aAttribs.Empty() );
		
		Destroy();

		FOR( i, _aVAO ) {
			_aVAO[i]	= i < aAttribs.Count() ? GLVertexAttribExt( uVBO, aAttribs[i] ) : GLVertexAttribExt();
			_aVAInfo[i]	= i < aAttribs.Count() ? GLVertexAttribInfo( aAttribs[i] ) : GLVertexAttribInfo();
		}
		
		_uIBO		= uIBO;
		_uOffset	= uOffset;
		_uCount		= uCount;
		_ePrimitive	= eMode;
		_eIndex		= eType;

		return true;
	}
	
/*
=================================================

=================================================
*/
	bool GLSubMesh::GetParams(GLVerticesAttribsExt_t &aAttribs, GLuint &uIBO, usize &uCount,
								usize &uOffset, gl_primitive::type &eMode, gl_index::type &eType) const
	{
		aAttribs.Reserve( _aVAO.Count() );

		FOR( i, _aVAO ) {
			aAttribs.PushBack( _aVAO[i] );
		}
		
		uIBO	= _uIBO;
		uCount	= _uCount;
		uOffset	= _uOffset;
		eMode	= _ePrimitive;
		eType	= _eIndex;
		return true;
	}

/*
=================================================
	удалить VAO
=================================================
*/
	void GLSubMesh::Destroy()
	{
		FOR( i, _aVAO ) {
			_aVAO[i]	= GLVertexAttribExt();
			_aVAInfo[i]	= GLVertexAttribInfo();
		}

		_uIBO		= 0;
		_uOffset	= 0;
		_uCount		= 0;
		_ePrimitive	= gl_primitive::TRIANGLE;
		_eIndex		= gl_index::UINT_32;
	}

#endif	// UX_GL_USE_VAO


	
//---------------------------- GLMesh -----------------------------//
/*
=================================================
	
=================================================
*/
	GLMesh::GLMesh()
	{
	}
	
/*
=================================================
	
=================================================
*/
	GLMesh::~GLMesh()
	{
	}
	
/*
=================================================
	
=================================================
*/
	bool GLMesh::Create()
	{
		Destroy();
		CHECK_ERR( _cVB.Create( gl_buffer::ARRAY ) and
					  _cIB.Create( gl_buffer::ELEMENT_ARRAY ) );
		return true;
	}
	
/*
=================================================
	
=================================================
*/
	void GLMesh::Destroy()
	{
		_cVB.Destroy();
		_cIB.Destroy();
		_aSubMeshes.Clear();
	}
	
/*
=================================================
	
=================================================
*/
	bool GLMesh::CreateSubMesh(const uni_array<const GLVertexAttrib> &aAttribs, uint uCount,
								usize uOffset, gl_primitive::type eMode, gl_index::type eType)
	{
		GLSubMesh	s_submesh;
		CHECK_ERR( s_submesh.Create( aAttribs, _cVB.Id(), _cIB.Id(), uCount, uOffset, eMode, eType ) );

		_aSubMeshes.PushBack( s_submesh );
		return true;
	}

/*
=================================================
	
=================================================
*/
	bool GLMesh::SetData(const void *pVertices, usize uVSize, const void *pIndices, usize uISize, gl_usage::type eUsage)
	{
		CHECK_ERR( _cVB.SetData( pVertices, uVSize, eUsage ) and
					  _cIB.SetData( pIndices, uISize, eUsage ) );
		return true;
	}
	
/*
=================================================
	
=================================================
*/
	bool GLMesh::AddSubMesh(GLSubMesh &sSubMesh)
	{
		_aSubMeshes.PushBack( sSubMesh );
		return true;
	}
	
/*
=================================================
	
=================================================
*/
	void GLMesh::DrawAll()
	{
		FOR( i, _aSubMeshes )
		{
			_aSubMeshes[i].DrawElements();
		}
	}
	
/*
=================================================
	
=================================================
*/
	void GLMesh::Draw(usize uSubMesh) const
	{
		_aSubMeshes[ uSubMesh ].DrawElements();
	}

//-------------------------------------------------------------------

}	// GLShell
}	// UX_STL